home *** CD-ROM | disk | FTP | other *** search
/ Shareware Grab Bag / Shareware Grab Bag.iso / 011 / treedupl.arc / TREEDUPL.PAS < prev    next >
Pascal/Delphi Source File  |  1986-09-05  |  59KB  |  1,771 lines

  1. PROGRAM TreeDuplicate;                                                  {008}
  2.  
  3. CONST
  4.  
  5.     VersionIdentification = '2.0';
  6.  
  7. (***********************************************************************
  8.  
  9.  
  10.     This software has been placed into the public domain by Digital
  11.                          Equipment Corporation.
  12.  
  13. DISCLAIMER:
  14.  
  15. The information herein is subject to change without  notice  and  should
  16. not be construed as a commitment by Digital Equipment Corporation.
  17.  
  18. Digital Equipment Corporation assumes no responsibility for the  use  or
  19. reliability  of  this  software.   This  software  is  provided "as is,"
  20. without any warranty of any kind, express or implied.  Digital Equipment
  21. Corporation  will  not  be liable in any event for any damages including
  22. any loss of data, profit, or savings, claims against  the  user  by  any
  23. other  party,  or  any other incidental or consequential damages arising
  24. out of the use of, or inability to use, this software, even  if  Digital
  25. Equipment Corporation is advised of the possibility of such damage.
  26.  
  27. DEFECT REPORTING AND SUGGESTIONS:
  28.  
  29. Please send reports of defects or suggestions for  improvement  directly
  30. to the author:
  31.  
  32.         Brian Hetrick
  33.         Digital Equipment Corporation
  34.         110 Spit Brook Road  ZKO1-3/J10
  35.         Nashua NH  03062-2698
  36.  
  37. Do NOT file a Software Performance Report on  this  software,  call  the
  38. Telephone  Support  Center regarding this software, contact your Digital
  39. Field Office  regarding  this  software,  or  use  any  other  mechanism
  40. provided for Digital's supported and warranted software.
  41.  
  42.  
  43. FACILITY:
  44.  
  45.     General user utilities
  46.  
  47. ABSTRACT:
  48.  
  49.     Duplicates one directory tree into another, attempting not  to  copy{008}
  50.     data if possible.  Intended for use as a backup utility using a DEC-
  51.     net-DOS virtual disk as the backup medium.
  52.  
  53. ENVIRONMENT:
  54.  
  55.     MS-DOS compiled with Borland International's TURBO Pascal
  56.  
  57. AUTHOR: Brian Hetrick, CREATION DATE: 27 May 1986.
  58.  
  59. MODIFICATION HISTORY:
  60.  
  61.         Brian Hetrick, 27-May-86: Version 1.0
  62.   000 - Original creation of module.
  63.         Released to Easynet 28-May-86.
  64.  
  65.         Brian Hetrick, 30-May-86: Version 1.1
  66.   001 - Attributes on directories were not updated.  Cause was that dir-
  67.         ectory modification date cannot be set, and IDAttrMatch  routine
  68.         was  testing  modification  date  for directories.  Main program
  69.         then attempted to replace the target directory, but  ReplaceFile
  70.         simply  returned.   Fix  is to have IDAttrMatch not look at mod-
  71.         ification dates for directories;  main program now  uses  Match-
  72.         File to update the attributes.
  73.   002 - Included program name and version in banner.
  74.         Released to Easynet 30-May-86
  75.  
  76.         Brian Hetrick, 31-May-86: Version 1.2
  77.   003 - Introduce hook for having files accumulate on target volume,  to
  78.         match hook for event logging.
  79.   004 - Introduce procedure to check for MS-DOS error, instead of always
  80.         explicitly checking low bit of returned Flags register.
  81.   005 - Introduce function to form name from  root  directory,  relative
  82.         directory,  and  file  in relative directory, rather than always
  83.         building directly from volume letter,  absolute  directory,  and
  84.         file  in absolute directory, as a hook for later permitting root
  85.         to be any directory.
  86.   006 - Avoid exteraneous copy in ExpandDirectory.
  87.   007 - Use only ASCII in message  text--replace  MCS  copyright  symbol
  88.         with (c) as program may run on IBM PCs without MCS.
  89.         Not released to Easynet as no user-visible improvements.
  90.  
  91.         Brian Hetrick, 03-Jun-86: Version 2.0
  92.   008 - Change name from VOLCOPY to TREEDUPL, as  now  will  copy  trees
  93.         rooted at other than the volume root directory.
  94.   009 - Use Bela Lubkin's public domain CommandLineArgument  routine  to
  95.         parse the command line.
  96.   010 - Deleted copyright notice as program will be submitted  to  DECUS
  97.         program library.
  98.         Released to Easynet on 3 June 1986.
  99.         Submitted to DECUS Program Library in September 1986.
  100.  
  101. ***********************************************************************)
  102. {.PA}
  103. (*
  104.  *  INCLUDE FILES:
  105.  *)
  106.  
  107. {$I CLA.PAS}                                                            {009}
  108.  
  109. (*
  110.  *  LABEL DECLARATIONS:
  111.  *)
  112.  
  113. (*
  114.  *  CONSTANT DECLARATIONS:
  115.  *)
  116.  
  117. CONST
  118.  
  119.     DOSFunctionChangeAttributes     = $43;
  120.     DOSFunctionCloseFile            = $3E;
  121.     DOSFunctionCreateFile           = $3C;
  122.     DOSFunctionCreateSubDirectory   = $39;
  123.     DOSFunctionDeleteDirectoryEntry = $41;
  124.     DOSFunctionFindMatchFile        = $4E;
  125.     DOSFunctionGetDTA               = $2F;
  126.     DOSFunctionOpenFile             = $3D;
  127.     DOSFunctionReadFromFile         = $3F;
  128.     DOSFunctionRemoveDirectoryEntry = $3A;
  129.     DOSFunctionSetDTA               = $1A;
  130.     DOSFunctionSetFileDateTime      = $57;
  131.     DOSFunctionStepThroughDirectory = $4F;
  132.     DOSFunctionWriteToFile          = $40;
  133.  
  134. CONST
  135.  
  136.     DirectoryAttrMask  = $10;   { Attribute bit for directory          }
  137.     DirectoryEntrySize = 5;     { Base length of DirectoryEntry        }
  138.     FileEntrySize      = 20;    { Base length of FileEntry             }
  139.     FileSpecLength     = 12;    { Length of MS-DOS base name           }
  140.     PathSpecLength     = 127;   { Length of MS-DOS path specification  }
  141.     ReadOnlyAttrMask   = $01;   { Attribute bit for read-only          }
  142.  
  143. (*
  144.  *  TYPE DECLARATIONS:
  145.  *)
  146.  
  147. TYPE
  148.  
  149.     FileSpec = STRING [FileSpecLength];
  150.  
  151.     PathSpec = STRING [PathSpecLength];
  152.  
  153.     DirectoryEntryPtr = ^ DirectoryEntry;
  154.  
  155.     DirectoryEntry = RECORD
  156.         Next : DirectoryEntryPtr;
  157.         Name : PathSpec
  158.         END;
  159.  
  160.     FileEntryPtr = ^ FileEntry;
  161.  
  162.     FileEntry = RECORD
  163.         Next : FileEntryPtr;
  164.         Prev : FileEntryPtr;
  165.         Size : REAL;
  166.         Time : INTEGER;
  167.         Date : INTEGER;
  168.         Attr : BYTE;
  169.         Name : FileSpec
  170.         END;
  171.  
  172.     FileEntryQueue = RECORD
  173.         Head : FileEntryPtr;
  174.         Tail : FileEntryPtr
  175.         END;
  176.  
  177.     RegPack = RECORD
  178.         CASE INTEGER OF
  179.          0: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags : INTEGER);
  180.          1: (AL, AH, BL, BH, CL, CH, DL, DH            : BYTE)
  181.         END;
  182.  
  183. (*
  184.  *  OWN STORAGE:
  185.  *)
  186.  
  187. VAR
  188.  
  189.     Accumulating : BOOLEAN;
  190.     Logging      : BOOLEAN;
  191.     SourceRoot   : PathSpec;
  192.     TargetRoot   : PathSpec;
  193.  
  194. (*
  195.  *  TABLE OF CONTENTS:
  196.  *)
  197. {.PA}
  198. PROCEDURE ParseCommandLine;                                             {009}
  199.  
  200. (***********************************************************************{009}
  201.  
  202. FUNCTIONAL DESCRIPTION:                                                 {009}
  203.  
  204.     Parses the program command line.                                    {009}
  205.  
  206. FORMAL PARAMETERS:                                                      {009}
  207.  
  208.     None.                                                               {009}
  209.  
  210. RETURN VALUE:                                                           {009}
  211.  
  212.     None.                                                               {009}
  213.  
  214. IMPLICIT INPUTS:                                                        {009}
  215.  
  216.     None.                                                               {009}
  217.  
  218. IMPLICIT OUTPUTS:                                                       {009}
  219.  
  220.     Accumulating - The BOOLEAN telling whether files on the  target  are{009}
  221.         to be retained if they are not on the source.                   {009}
  222.     Logging - The BOOLEAN telling whether messages informing the user of{009}
  223.         actions taken are to be written to the standard output.         {009}
  224.     SourceRoot - The root directory of the source directory tree.       {009}
  225.     TargetRoot - The root directory of the target directory tree.       {009}
  226.  
  227. SIDE EFFECTS:
  228.  
  229.     Will Halt the program if an error in the command line is discovered.{009}
  230.  
  231. ***********************************************************************){009}
  232.  
  233.     VAR                                                                 {009}
  234.  
  235.         CharIndex    : INTEGER;                                         {009}
  236.         CommandValid : BOOLEAN;                                         {009}
  237.         SwitchSense  : BOOLEAN;                                         {009}
  238.         SwitchText   : BigString;                                       {009}
  239.  
  240.     FUNCTION IsPrefix (Str1 : BigString; Str2 : BigString) : BOOLEAN;   {009}
  241.  
  242.         VAR                                                             {009}
  243.  
  244.             CharIndex : INTEGER;                                        {009}
  245.  
  246.         BEGIN                                                           {009}
  247.  
  248.         IF Length (Str1) > Length (Str2)                                {009}
  249.         THEN                                                            {009}
  250.             IsPrefix := FALSE                                           {009}
  251.         ELSE                                                            {009}
  252.             IsPrefix := Str1 = Copy (Str1, 1, Length (Str1))
  253.  
  254.         END;                                                            {009}
  255.  
  256.     PROCEDURE UpCaseString (VAR Str : PathSpec);                        {009}
  257.  
  258.         VAR                                                             {009}
  259.  
  260.             CharIndex : INTEGER;                                        {009}
  261.  
  262.         BEGIN                                                           {009}
  263.  
  264.         FOR CharIndex := 1 TO Length (Str)                              {009}
  265.         DO                                                              {009}
  266.             Str [CharIndex] := UpCase (Str [CharIndex])                 {009}
  267.  
  268.         END;                                                            {009}
  269.  
  270.     BEGIN                                                               {009}
  271.  
  272.     (*                                                                  {009}
  273.      *  Get source and destination roots                                {009}
  274.      *)                                                                 {009}
  275.  
  276.     SourceRoot := CommandLineArgument                                   {009}
  277.         ('Source directory: ', '/', FALSE);                             {009}
  278.     UpCaseString (SourceRoot);                                          {009}
  279.     TargetRoot := CommandLineArgument                                   {009}
  280.         ('Destination directory: ', '/', FALSE);                        {009}
  281.     UpCaseString (TargetRoot);                                          {009}
  282.  
  283.     (*                                                                  {009}
  284.      *  Set defaults                                                    {009}
  285.      *)                                                                 {009}
  286.  
  287.     Accumulating := TRUE;                                               {009}
  288.     Logging      := TRUE;                                               {009}
  289.  
  290.     (*                                                                  {009}
  291.      *  Process switches                                                {009}
  292.      *)                                                                 {009}
  293.  
  294.     CommandValid := TRUE;                                               {009}
  295.     SwitchText := CommandLineArgument ('', '', TRUE);                   {009}
  296.     WHILE CommandValid AND (Length (SwitchText) > 0)                    {009}
  297.     DO                                                                  {009}
  298.         BEGIN                                                           {009}
  299.  
  300.         UpCaseString (SwitchText);                                      {009}
  301.  
  302.         (*                                                              {009}
  303.          *  Get rid of the leading slash                                {009}
  304.          *)                                                             {009}
  305.  
  306.         Delete (SwitchText, 1, 1);                                      {009}
  307.         IF Length (SwitchText) = 0                                      {009}
  308.         THEN                                                            {009}
  309.             BEGIN                                                       {009}
  310.             WriteLn ('Invalid switch: "/"');                            {009}
  311.             CommandValid := FALSE;                                      {009}
  312.             END;                                                        {009}
  313.  
  314.         IF CommandValid                                                 {009}
  315.         THEN                                                            {009}
  316.  
  317.             (*                                                          {009}
  318.              *  Check for "NO" prefix                                   {009}
  319.              *)                                                         {009}
  320.  
  321.             IF Copy (SwitchText, 1, 2) = 'NO'                           {009}
  322.             THEN                                                        {009}
  323.                 BEGIN                                                   {009}
  324.                 SwitchSense := FALSE;                                   {009}
  325.                 Delete (SwitchText, 1, 2);                              {009}
  326.                 IF Length (SwitchText) = 0                              {009}
  327.                 THEN                                                    {009}
  328.                     BEGIN                                               {009}
  329.                     WriteLn ('Invalid switch: "/NO"');                  {009}
  330.                     CommandValid := FALSE                               {009}
  331.                     END                                                 {009}
  332.                 END                                                     {009}
  333.             ELSE                                                        {009}
  334.                 SwitchSense := TRUE;                                    {009}
  335.  
  336.         IF CommandValid                                                 {009}
  337.         THEN                                                            {009}
  338.             BEGIN                                                       {009}
  339.  
  340.             (*                                                          {009}
  341.              *  Check for switch names                                  {009}
  342.              *)                                                         {009}
  343.  
  344.             IF IsPrefix (SwitchText, 'LOG')                             {009}
  345.             THEN                                                        {009}
  346.  
  347.                 Logging := SwitchSense                                  {009}
  348.  
  349.             ELSE IF IsPrefix (SwitchText, 'ACCUMULATE')                 {009}
  350.             THEN                                                        {009}
  351.  
  352.                 Accumulating := SwitchSense                             {009}
  353.  
  354.             ELSE                                                        {009}
  355.                 BEGIN                                                   {009}
  356.  
  357.                 Write ('Invalid switch: "/');                           {009}
  358.                 IF SwitchSense = FALSE                                  {009}
  359.                 THEN                                                    {009}
  360.                     Write ('NO');                                       {009}
  361.                 WriteLn (SwitchText, '"');                              {009}
  362.                 CommandValid := FALSE                                   {009}
  363.  
  364.                 END                                                     {009}
  365.  
  366.             END;                                                        {009}
  367.  
  368.         IF CommandValid                                                 {009}
  369.         THEN                                                            {009}
  370.             SwitchText := CommandLineArgument ('', '', TRUE)            {009}
  371.  
  372.         END;                                                            {009}
  373.  
  374.     IF NOT CommandValid                                                 {009}
  375.         THEN                                                            {009}
  376.         Halt                                                            {009}
  377.  
  378.     END;                                                                {009}
  379. {.PA}
  380. FUNCTION ErrorReturn                                                    {004}
  381.    (    Registers : RegPack) : BOOLEAN;                                 {004}
  382.  
  383. (***********************************************************************{004}
  384.  
  385. FUNCTIONAL DESCRIPTION:                                                 {004}
  386.  
  387.     Checks a set of registers returned from the MsDos procedure and  de-{004}
  388.     termines whether the function completed successfully.               {004}
  389.  
  390. FORMAL PARAMETERS:                                                      {004}
  391.  
  392.     Registers - A RegPack expression giving the register values returned{004}
  393.         by the MsDos procedure.                                         {004}
  394.  
  395. RETURN VALUE:                                                           {004}
  396.  
  397.     TRUE - The MsDos function failed.                                   {004}
  398.     FALSE - The MsDos function succeeded.                               {004}
  399.  
  400. IMPLICIT INPUTS:                                                        {004}
  401.  
  402.     None.                                                               {004}
  403.  
  404. IMPLICIT OUTPUTS:                                                       {004}
  405.  
  406.     None.                                                               {004}
  407.  
  408. SIDE EFFECTS:                                                           {004}
  409.  
  410.     None.                                                               {004}
  411.  
  412. ***********************************************************************){004}
  413.  
  414.     BEGIN                                                               {004}
  415.  
  416.     ErrorReturn := (Registers . Flags AND 1) <> 0                       {004}
  417.  
  418.     END;                                                                {004}
  419. {.PA}
  420. FUNCTION ConstructFileName                                              {005}
  421.    (    RootDirectory     : PathSpec;                                   {005}
  422.         RelativeDirectory : PathSpec;                                   {005}
  423.         FileName          : FileSpec) : PathSpec;                       {005}
  424.  
  425. (***********************************************************************{005}
  426.  
  427. FUNCTIONAL DESCRIPTION:                                                 {005}
  428.  
  429.     Constructs a path specification from a root  directory,  a  relative{005}
  430.     directory, and file name by concatenating these elements, separating{005}
  431.     them by backslash if there is not already a separator.              {005}
  432.  
  433. FORMAL PARAMETERS:                                                      {005}
  434.  
  435.     RootDirectory - A PathSpec expression giving the root  directory  of{005}
  436.         the eventual path specification.                                {005}
  437.     RelativeDirectory -  A  PathSpec  expression  giving  the  directory{005}
  438.         relative to RootDirectory of the eventual path specification.   {005}
  439.     FileName - A FileSpec expression giving the file name of  the  even-{005}
  440.         tual path specification.                                        {005}
  441.  
  442. RETURN VALUE:                                                           {005}
  443.  
  444.     The resultant path specification.                                   {005}
  445.  
  446. IMPLICIT INPUTS:                                                        {005}
  447.  
  448.     None.                                                               {005}
  449.  
  450. IMPLICIT OUTPUTS:                                                       {005}
  451.  
  452.     None.                                                               {005}
  453.  
  454. SIDE EFFECTS:                                                           {005}
  455.  
  456.     None.                                                               {005}
  457.  
  458. ***********************************************************************){005}
  459.  
  460.     CONST                                                               {005}
  461.         Separator : SET OF CHAR = [':', '\', '/'];                      {005}
  462.  
  463.     VAR                                                                 {005}
  464.         TempName : PathSpec;                                            {005}
  465.  
  466.     BEGIN                                                               {005}
  467.  
  468.     TempName := RootDirectory;                                          {005}
  469.  
  470.     IF (Length (TempName) > 0) AND (Length (RelativeDirectory) > 0)     {005}
  471.     THEN                                                                {005}
  472.         IF NOT (TempName [Length (TempName)] IN Separator)              {005}
  473.         THEN                                                            {005}
  474.             Insert ('\', TempName, Length (TempName) + 1);              {005}
  475.  
  476.     Insert (RelativeDirectory, TempName, Length (TempName) + 1);        {005}
  477.  
  478.     IF (Length (TempName) > 0) AND (Length (FileName) > 0)              {005}
  479.     THEN                                                                {005}
  480.         IF NOT (TempName [Length (TempName)] IN Separator)              {005}
  481.         THEN                                                            {005}
  482.             Insert ('\', TempName, Length (TempName) + 1);              {005}
  483.  
  484.     Insert (FileName, TempName, Length (TempName) + 1);                 {005}
  485.  
  486.     ConstructFileName := TempName                                       {005}
  487.  
  488.     END;                                                                {005}
  489. {.PA}
  490. PROCEDURE ExpandDirectory
  491.    (    RootDirectory     : PathSpec;                                   {005}
  492.         DirectoryToExpand : DirectoryEntryPtr;
  493.     VAR FileQueue         : FileEntryQueue);
  494.  
  495. (***********************************************************************
  496.  
  497. FUNCTIONAL DESCRIPTION:
  498.  
  499.     Finds and lexicographically sorts the names of all files  in  a  di-
  500.     rectory
  501.  
  502. FORMAL PARAMETERS:
  503.  
  504.     RootDirectory - A PathSpec expression giving the root  directory  to{005}
  505.         which DirectoryName is a relative directory.                    {005}
  506.     DirectoryName - A DirectoryEntryPtr expression pointing to  the  Di-
  507.         recoryEntry describing the directory to be examined
  508.     FileQueue - A FileEntryQueue object which is modified to point to  a
  509.         newly created queue of the names of files in the directory
  510.  
  511. RETURN VALUE:
  512.  
  513.     None.
  514.  
  515. IMPLICIT INPUTS:
  516.  
  517.     None.
  518.  
  519. IMPLICIT OUTPUTS:
  520.  
  521.     None.
  522.  
  523. SIDE EFFECTS:
  524.  
  525.     Modifies and resets the DTA.  This should be observable only by  in-
  526.     terrupt routines.
  527.  
  528.     Dynamically allocates storage with GetMem.
  529.  
  530. ***********************************************************************)
  531.  
  532.     VAR
  533.  
  534.         FoundPos       : BOOLEAN;
  535.         FileNameLength : INTEGER;
  536.         FileName       : FileSpec;
  537.         MSDOSBlock     : RECORD
  538.             Reserved   : ARRAY [1..21] OF BYTE;
  539.             Attribute  : BYTE;
  540.             Time       : INTEGER;
  541.             Date       : INTEGER;
  542.             SizeLow    : INTEGER;
  543.             SizeHigh   : INTEGER;
  544.             Name       : ARRAY [1..13] OF CHAR
  545.             END;
  546.         NextFile       : FileEntryPtr;
  547.         OldDTA         : ^ CHAR;
  548.         PrevFile       : FileEntryPtr;
  549.         Registers      : RegPack;
  550.         SearchSpec     : PathSpec;
  551.         ThisFile       : FileEntryPtr;
  552.  
  553.     BEGIN
  554.  
  555.     (*
  556.      *  Initialize the file queue
  557.      *)
  558.  
  559.     FileQueue . Head := NIL;
  560.     FileQueue . Tail := NIL;
  561.  
  562.     (*
  563.      *  Save the old DTA
  564.      *)
  565.  
  566.     Registers.AH := DOSFunctionGetDTA;
  567.     MsDos (Registers);
  568.     OldDTA := Ptr (Registers.ES, Registers.BX);
  569.  
  570.     (*
  571.      *  Set the DTA to be the MS-DOS information block
  572.      *)
  573.  
  574.     Registers.AH := DOSFunctionSetDTA;
  575.     Registers.DS := Seg (MSDOSBlock);
  576.     Registers.DX := Ofs (MSDOSBlock);
  577.     MsDos (Registers);
  578.  
  579.     (*
  580.      *  Find the contents of the directory
  581.      *)
  582.  
  583.     SearchSpec := ConstructFileName (RootDirectory,                     {005}
  584.         DirectoryToExpand ^. Name, '*.*');                              {005}
  585.     SearchSpec [Length (SearchSpec) + 1] := #$00;                       {005}
  586.  
  587.     Registers.AH := DOSFunctionFindMatchFile;
  588.     Registers.DS := Seg (SearchSpec [1]);
  589.     Registers.DX := Ofs (SearchSpec [1]);
  590.     Registers.CX := $37;
  591.     MsDos (Registers);
  592.  
  593.     WHILE NOT ErrorReturn (Registers)                                   {004}
  594.     DO
  595.         BEGIN
  596.  
  597.         (*
  598.          *  Extract the file name
  599.          *)
  600.  
  601.         FileNameLength := 1;
  602.         WHILE MSDOSBlock . Name [FileNameLength] <> #$00
  603.         DO
  604.             FileNameLength := FileNameLength + 1;
  605.         FileNameLength := FileNameLength - 1;
  606.         FileName := Copy (MSDOSBlock . Name, 1, FileNameLength);
  607.  
  608.         (*
  609.          *  Ignore relative directories
  610.          *)
  611.  
  612.         IF (FileName <> '.') AND (FileName <> '..')
  613.         THEN
  614.             BEGIN
  615.  
  616.             (*
  617.              *  Create a file entry for this file
  618.              *)
  619.  
  620.             GetMem (ThisFile, FileEntrySize + FileNameLength);
  621.  
  622.             ThisFile ^. Attr := MSDOSBlock . Attribute;
  623.             ThisFile ^. Time := MSDOSBlock . Time;
  624.             ThisFile ^. Date := MSDOSBlock . Date;
  625.             IF MSDOSBlock . SizeHigh < 0
  626.             THEN
  627.                 ThisFile ^. Size := MSDOSBlock . SizeHigh + 65536.0
  628.             ELSE
  629.                 ThisFile ^. Size := MSDOSBlock . SizeHigh;
  630.             ThisFile ^. Size := ThisFile ^. Size * 65536.0;
  631.             IF MSDOSBlock . SizeLow < 0
  632.             THEN
  633.                 ThisFile ^. Size := ThisFile ^. Size +
  634.                     MSDOSBlock . SizeLow + 65536.0
  635.             ELSE
  636.                 ThisFile ^. Size := ThisFile ^. Size +
  637.                     MSDOSBlock . SizeLow;
  638.  
  639.             ThisFile ^. Name := FileName;                               {006}
  640.  
  641.             (*
  642.              *  Insert the newly allocated entry into the sorted queue
  643.              *)
  644.  
  645.             NextFile := FileQueue . Head;
  646.             PrevFile := NIL;
  647.             FoundPos := FALSE;
  648.             WHILE NOT FoundPos
  649.             DO
  650.                 BEGIN
  651.                 IF NextFile = NIL
  652.                 THEN
  653.                     FoundPos := TRUE
  654.                 ELSE
  655.                     IF NextFile ^. Name > ThisFile ^. Name
  656.                     THEN
  657.                         FoundPos := TRUE
  658.                     ELSE
  659.                         BEGIN
  660.                         PrevFile := NextFile;
  661.                         NextFile := NextFile ^. Next
  662.                         END
  663.                 END;
  664.  
  665.             ThisFile ^. Prev := PrevFile;
  666.             IF PrevFile = NIL
  667.             THEN
  668.                 FileQueue . Head := ThisFile
  669.             ELSE
  670.                 PrevFile ^. Next := ThisFile;
  671.             ThisFile ^. Next := NextFile;
  672.             IF NextFile = NIL
  673.             THEN
  674.                 FileQueue . Tail := ThisFile
  675.             ELSE
  676.                 NextFile ^. Prev := ThisFile
  677.  
  678.             END;
  679.  
  680.         (*
  681.          *  Get the next file in the directory
  682.          *)
  683.  
  684.         Registers.AH := DOSFunctionStepThroughDirectory;
  685.         MsDos (Registers)
  686.  
  687.         END;
  688.  
  689.     (*
  690.      *  The directory has been expanded.  Reset the DTA
  691.      *)
  692.  
  693.     Registers.AH := DOSFunctionSetDTA;
  694.     Registers.DS := Seg (OldDTA ^);
  695.     Registers.DX := Ofs (OldDTA ^);
  696.     MsDos (Registers)
  697.  
  698.     END;
  699. {.PA}
  700. PROCEDURE ExtractDirectories
  701.    (    CurrentDirectory : DirectoryEntryPtr;
  702.         FileQueue        : FileEntryQueue;
  703.     VAR DirectoryList    : DirectoryEntryPtr);
  704.  
  705. (***********************************************************************
  706.  
  707. FUNCTIONAL DESCRIPTION:
  708.  
  709.     Examines the contents of the current directory,  extracts  the  full
  710.     path  names  of  all  subdirectories,  and places these subdirectory
  711.     names on a queue of pending directories.
  712.  
  713. FORMAL PARAMETERS:
  714.  
  715.     CurrentDirectory - A DirectoryEntryPtr pointing to a  DirectoryEntry
  716.         describing the directory whose contents are given by FileQueue.
  717.     FileQueue - A FileEntryQueue pointing to a list of FileEntry objects
  718.         describing  the  files  in  the  directory described by Current-
  719.         Directory.
  720.     DirectoryList - A DirectoryEntryPtr pointing to a list of Directory-
  721.         Entry objects.  New DirectoryEntry objects are created  for  the
  722.         subdirectories found on the list of FileEntry objects pointed to
  723.         by FileQueue, and are placed onto this list.
  724.  
  725. RETURN VALUE:
  726.  
  727.     None.
  728.  
  729. IMPLICIT INPUTS:
  730.  
  731.     None.
  732.  
  733. IMPLICIT OUTPUTS:
  734.  
  735.     None.
  736.  
  737. SIDE EFFECTS:
  738.  
  739.     Dynamically allocates storage with GetMem.
  740.  
  741. ***********************************************************************)
  742.  
  743.     VAR
  744.  
  745.         DirectoryText : PathSpec;
  746.         ThisDirectory : DirectoryEntryPtr;
  747.         ThisEntry     : FileEntryPtr;
  748.  
  749.     BEGIN
  750.  
  751.     (*
  752.      *  Scan list backwards, looking for directories
  753.      *)
  754.  
  755.     ThisEntry := FileQueue . Tail;
  756.  
  757.     WHILE ThisEntry <> NIL
  758.     DO
  759.         BEGIN
  760.  
  761.         IF (ThisEntry ^. Attr AND DirectoryAttrMask) <> 0
  762.         THEN
  763.             BEGIN
  764.  
  765.             (*
  766.              *  This entry is a directory.
  767.              *)
  768.  
  769.             DirectoryText :=                                            {005}
  770.                 ConstructFileName (CurrentDirectory ^. Name,            {005}
  771.                     ThisEntry ^. Name, '');                             {005}
  772.             GetMem (ThisDirectory, DirectoryEntrySize +
  773.                 Length (DirectoryText));
  774.             ThisDirectory ^. Next := DirectoryList;
  775.             ThisDirectory ^. Name := DirectoryText;
  776.             DirectoryList := ThisDirectory
  777.  
  778.             END;
  779.  
  780.         ThisEntry := ThisEntry ^. Prev
  781.  
  782.         END
  783.  
  784.     END;
  785. {.PA}
  786. PROCEDURE AdvanceFile
  787.    (VAR FileQueue : FileEntryQueue);
  788.  
  789. (***********************************************************************
  790.  
  791. FUNCTIONAL DESCRIPTION:
  792.  
  793.     Deletes the first item on a file entry queue.
  794.  
  795. FORMAL PARAMETERS:
  796.  
  797.     FileQueue - A FileEntryQueue object pointing to a queue of FileEntry
  798.         objects.  The item pointed at by the Head  pointer  is  deleted,
  799.         and the queue is adjusted for this deletion.
  800.  
  801. RETURN VALUE:
  802.  
  803.     None.
  804.  
  805. IMPLICIT INPUTS:
  806.  
  807.     None.
  808.  
  809. IMPLICIT OUTPUTS:
  810.  
  811.     None.
  812.  
  813. SIDE EFFECTS:
  814.  
  815.     Dynamically frees storage with FreeMem.
  816.  
  817. ***********************************************************************)
  818.  
  819.     VAR
  820.         ThisEntry : FileEntryPtr;
  821.  
  822.     BEGIN
  823.  
  824.     (*
  825.      *  Ensure that there is an item to delete
  826.      *)
  827.  
  828.     ThisEntry := FileQueue . Head;
  829.     IF ThisEntry <> NIL
  830.     THEN
  831.         BEGIN
  832.  
  833.         (*
  834.          *  There is.  First, relink the queue around the item
  835.          *)
  836.  
  837.         FileQueue . Head := ThisEntry ^. Next;
  838.         IF FileQueue . Head = NIL
  839.         THEN
  840.             FileQueue . Tail := NIL
  841.         ELSE
  842.             FileQueue . Head ^. Prev := NIL;
  843.  
  844.         (*
  845.          *  Now free the item's storage
  846.          *)
  847.  
  848.         FreeMem (ThisEntry, FileEntrySize + Length (ThisEntry ^. Name))
  849.  
  850.         END
  851.  
  852.     END;
  853. {.PA}
  854. FUNCTION IDAttrMatch
  855.    (    FileEntry1 : FileEntryPtr;
  856.         FileEntry2 : FileEntryPtr) : BOOLEAN;
  857.  
  858. (***********************************************************************
  859.  
  860. FUNCTIONAL DESCRIPTION:
  861.  
  862.     Determine whether two files are putatively identical.
  863.  
  864.     Two files are considered to be identical if they have the same name,
  865.     same directory attribute, and, in the case of  non-directory  files,{001}
  866.     the  same  creation/modification  date  and  time and size.  NO COM-{001}
  867.     PARISON OF THE FILE CONTENTS IS MADE.
  868.  
  869. FORMAL PARAMETERS:
  870.  
  871.     File1Desc - A FileEntryPtr pointing to a FileEntry object describing
  872.         the first of the two files.
  873.     File2Desc - A FileEntryPtr pointing to a FileEntry object describing
  874.         the second of the two files.
  875.  
  876. RETURN VALUE:
  877.  
  878.     TRUE - The files are considered to be identical.
  879.     FALSE - The files are not considered to be identical.
  880.  
  881. IMPLICIT INPUTS:
  882.  
  883.     None.
  884.  
  885. IMPLICIT OUTPUTS:
  886.  
  887.     None.
  888.  
  889. SIDE EFFECTS:
  890.  
  891.     None.
  892.  
  893. ***********************************************************************)
  894.  
  895.     VAR
  896.  
  897.         Difference : BOOLEAN;
  898.  
  899.     BEGIN
  900.  
  901.     Difference := FALSE;
  902.  
  903.     IF FileEntry1 ^. Name <> FileEntry2 ^. Name
  904.     THEN
  905.         Difference := TRUE;
  906.  
  907.     IF (FileEntry1 ^. Attr AND DirectoryAttrMask) <>
  908.        (FileEntry2 ^. Attr AND DirectoryAttrMask)
  909.     THEN
  910.         Difference := TRUE;
  911.  
  912.     IF (FileEntry1 ^. Attr AND DirectoryAttrMask) = 0                   {001}
  913.     THEN                                                                {001}
  914.         BEGIN                                                           {001}
  915.  
  916.         IF FileEntry1 ^. Time <> FileEntry2 ^. Time
  917.         THEN
  918.             Difference := TRUE;
  919.  
  920.         IF FileEntry1 ^. Date <> FileEntry2 ^. Date
  921.         THEN
  922.             Difference := TRUE;
  923.  
  924.         IF FileEntry1 ^. Size <> FileEntry2 ^. Size
  925.         THEN
  926.             Difference := TRUE                                          {001}
  927.  
  928.         END;                                                            {001}
  929.  
  930.     IDAttrMatch := NOT Difference
  931.  
  932.     END;
  933. {.PA}
  934. PROCEDURE DeleteFile
  935.    (    RootDirectory    : PathSpec;                                    {005}
  936.         CurrentDirectory : DirectoryEntryPtr;
  937.         FileInfo         : FileEntryPtr);
  938.  
  939. (***********************************************************************
  940.  
  941. FUNCTIONAL DESCRIPTION:
  942.  
  943.     Deletes a single file or an entire subdirectory tree.  When deleting
  944.     an entire subdirectory tree, recurses to the depth of the subdirect-
  945.     ory tree.
  946.  
  947. FORMAL PARAMETERS:
  948.  
  949.     RootDirectory - A PathSpec expression giving the root  directory  to{005}
  950.         which DirectoryName is a relative directory.                    {005}
  951.     CurrentDirectory - A DirectoryEntryPtr expression pointing to a  Di-
  952.         rectoryEntry  object  describing the directory in which the file
  953.         resides.
  954.     FileInformation - A FileEntryPtr expression pointing to a  FileEntry
  955.         object describing the file to be deleted.
  956.  
  957. RETURN VALUE:
  958.  
  959.     None.
  960.  
  961. IMPLICIT INPUTS:
  962.  
  963.     Logging - The BOOLEAN telling whether event logging is currently on.
  964.  
  965. IMPLICIT OUTPUTS:
  966.  
  967.     None.
  968.  
  969. SIDE EFFECTS:
  970.  
  971.     None.
  972.  
  973. ***********************************************************************)
  974.  
  975.     VAR
  976.  
  977.         NewDirEntry : DirectoryEntry;
  978.         Registers   : RegPack;
  979.         SubDirQueue : FileEntryQueue;
  980.  
  981.     (*
  982.      *  A DirectoryEntry is used in place of a PathSpec for the name  of
  983.      *  the  single file to be deleted, in order to minimize local stor-
  984.      *  age requirements.  This is important only  as  this  routine  is
  985.      *  recursive.
  986.      *)
  987.  
  988.     BEGIN
  989.  
  990.     (*
  991.      *  If the "file" to be deleted is a directory,  delete  the  entire
  992.      *  tree rooted there
  993.      *)
  994.  
  995.     IF (FileInfo ^. Attr AND DirectoryAttrMask) <> 0
  996.     THEN
  997.         BEGIN
  998.  
  999.         (*
  1000.          *  Construct a directory entry for the directory
  1001.          *)
  1002.  
  1003.         NewDirEntry . Name :=                                           {005}
  1004.             ConstructFileName (CurrentDirectory ^. Name,                {005}
  1005.                 FileInfo ^. Name, '');                                  {005}
  1006.  
  1007.         (*
  1008.          *  Get contents of directory
  1009.          *)
  1010.  
  1011.         ExpandDirectory (RootDirectory, Addr (NewDirEntry),             {005}
  1012.             SubDirQueue);
  1013.  
  1014.         (*
  1015.          *  Recursively delete the contents of the directory
  1016.          *)
  1017.  
  1018.         WHILE SubDirQueue . Head <> NIL
  1019.         DO
  1020.             BEGIN
  1021.  
  1022.             DeleteFile (RootDirectory, Addr (NewDirEntry),              {005}
  1023.                 SubDirQueue . Head);
  1024.             AdvanceFile (SubDirQueue)
  1025.  
  1026.             END
  1027.  
  1028.         END;
  1029.  
  1030.     (*
  1031.      *  Generate the file specification
  1032.      *)
  1033.  
  1034.     NewDirEntry . Name := ConstructFileName (RootDirectory,             {005}
  1035.         CurrentDirectory ^. Name, FileInfo ^. Name);                    {005}
  1036.  
  1037.     (*
  1038.      *  Put on the trailing NUL for MS-DOS calls
  1039.      *)
  1040.  
  1041.     NewDirEntry . Name [Length (NewDirEntry . Name) + 1] := #$00;
  1042.  
  1043.     (*
  1044.      *  The Read-Only attribute implies  that  the  file  cannot  be
  1045.      *  deleted.  If the Read-Only attribute is on, turn it off.
  1046.      *)
  1047.  
  1048.     IF (FileInfo ^. Attr AND ReadOnlyAttrMask) <> 0
  1049.     THEN
  1050.         BEGIN
  1051.  
  1052.         Registers . AH := DOSFunctionChangeAttributes;
  1053.         Registers . DS := Seg (NewDirEntry . Name [1]);
  1054.         Registers . DX := Ofs (NewDirEntry . Name [1]);
  1055.         Registers . CX := FileInfo ^. Attr AND NOT                      {001}
  1056.             (ReadOnlyAttrMask OR DirectoryAttrMask);                    {001}
  1057.         Registers . AL := 1;
  1058.         MsDos (Registers);
  1059.         IF ErrorReturn (Registers)                                      {004}
  1060.         THEN
  1061.             BEGIN
  1062.  
  1063.             WriteLn ('Cannot change attributes on ', NewDirEntry . Name);
  1064.             Halt
  1065.  
  1066.             END
  1067.  
  1068.         END;
  1069.  
  1070.     (*
  1071.      *  Actually delete the file
  1072.      *)
  1073.  
  1074.     IF (FileInfo ^. Attr AND DirectoryAttrMask) <> 0
  1075.     THEN
  1076.         Registers . AH := DOSFunctionRemoveDirectoryEntry
  1077.     ELSE
  1078.         Registers . AH := DOSFunctionDeleteDirectoryEntry;
  1079.  
  1080.     Registers . DS := Seg (NewDirEntry . Name [1]);
  1081.     Registers . DX := Ofs (NewDirEntry . Name [1]);
  1082.     MsDos (Registers);
  1083.     IF ErrorReturn (Registers)                                          {004}
  1084.     THEN
  1085.         BEGIN
  1086.  
  1087.         Write ('Cannot delete ');
  1088.         IF (FileInfo ^. Attr AND DirectoryAttrMask) <> 0
  1089.         THEN
  1090.             Write ('directory ');
  1091.         WriteLn (NewDirEntry . Name);
  1092.         Halt
  1093.  
  1094.         END;
  1095.  
  1096.     (*
  1097.      *  If logging is on, note the deletion
  1098.      *)
  1099.  
  1100.     IF Logging
  1101.     THEN
  1102.         WriteLn ('Deleted ', NewDirEntry . Name)
  1103.  
  1104.     END;
  1105. {.PA}
  1106. PROCEDURE CopyFile
  1107.    (    SourceRootDir    : PathSpec;                                    {005}
  1108.         CurrentDirectory : DirectoryEntryPtr;
  1109.         FileInfo         : FileEntryPtr;
  1110.         DestinRootDir    : PathSpec);                                   {005}
  1111.  
  1112. (***********************************************************************
  1113.  
  1114. FUNCTIONAL DESCRIPTION:
  1115.  
  1116.     Duplicates the source file on the destination.  This duplication al-{005}
  1117.     ways includes relative directory and file name, and file attributes.{005}
  1118.     In the case of non-directory files, this also includes  modification{005}
  1119.     date and time, and contents.                                        {005}
  1120.  
  1121. FORMAL PARAMETERS:
  1122.  
  1123.     SourceRootDirectory - A PathSpec expression giving the root  direct-{005}
  1124.         ory  to  which  DirectoryName  is  a  relative directory for the{005}
  1125.         source file.                                                    {005}
  1126.     CurrentDirectory - A DirectoryEntryPtr pointing to a  DirectoryEntry
  1127.         object describing the directory in which the source file resides
  1128.         and in which the target file is to reside.
  1129.     FileInfo - A FileEntryPtr pointing to a FileEntry object  describing
  1130.         the source file, and which is to describe the target file.
  1131.     TargetRootDirectory - A PathSpec expression giving the root  direct-{005}
  1132.         ory  to which DirectoryName is a relative directory for the tar-{005}
  1133.         get file.                                                       {005}
  1134.  
  1135. RETURN VALUE:
  1136.  
  1137.     None.
  1138.  
  1139. IMPLICIT INPUTS:
  1140.  
  1141.     Logging - The BOOLEAN telling whether event logging is currently on.
  1142.  
  1143. IMPLICIT OUTPUTS:
  1144.  
  1145.     None.
  1146.  
  1147. SIDE EFFECTS:
  1148.  
  1149.     None.
  1150.  
  1151. ***********************************************************************)
  1152.  
  1153.     CONST
  1154.  
  1155.         BufferSize = 1024;
  1156.  
  1157.     VAR
  1158.  
  1159.         CopyBuffer     : ARRAY [1..BufferSize] OF CHAR;
  1160.         DestinHandle   : INTEGER;
  1161.         DestinName     : PathSpec;
  1162.         Registers      : RegPack;
  1163.         SourceHandle   : INTEGER;
  1164.         SourceName     : PathSpec;
  1165.         TransferSize   : INTEGER;
  1166.  
  1167.     BEGIN
  1168.  
  1169.     (*
  1170.      *  Construct the source and destination file names
  1171.      *)
  1172.  
  1173.     SourceName := ConstructFileName (SourceRootDir,                     {005}
  1174.         CurrentDirectory ^. Name, FileInfo ^. Name);                    {005}
  1175.     DestinName := ConstructFileName (DestinRootDir,                     {005}
  1176.         CurrentDirectory ^. Name, FileInfo ^. Name);                    {005}
  1177.     SourceName [Length (SourceName) + 1] := #$00;
  1178.     DestinName [Length (DestinName) + 1] := #$00;
  1179.  
  1180.     (*
  1181.      *  Now copy the files
  1182.      *)
  1183.  
  1184.     IF (FileInfo ^. Attr AND DirectoryAttrMask) <> 0
  1185.     THEN
  1186.         BEGIN
  1187.  
  1188.         (*
  1189.          *  For a directory, simply create the target directory
  1190.          *)
  1191.  
  1192.         Registers . AH := DOSFunctionCreateSubDirectory;
  1193.         Registers . DS := Seg (DestinName [1]);
  1194.         Registers . DX := Ofs (DestinName [1]);
  1195.         MsDos (Registers);
  1196.         IF ErrorReturn (Registers)                                      {004}
  1197.         THEN
  1198.             BEGIN
  1199.  
  1200.             WriteLn ('Cannot create directory ', DestinName);
  1201.             Halt
  1202.  
  1203.             END
  1204.  
  1205.         END
  1206.     ELSE
  1207.         BEGIN
  1208.  
  1209.         (*
  1210.          *  For a file, copy the data and set the creation date and time
  1211.          *)
  1212.  
  1213.         Registers . AH := DOSFunctionOpenFile;
  1214.         Registers . AL := 0;
  1215.         Registers . DS := Seg (SourceName [1]);
  1216.         Registers . DX := Ofs (SourceName [1]);
  1217.         MsDos (Registers);
  1218.         IF ErrorReturn (Registers)                                      {004}
  1219.         THEN
  1220.             BEGIN
  1221.  
  1222.             WriteLn ('Cannot open ', SourceName);
  1223.             Halt
  1224.  
  1225.             END;
  1226.  
  1227.         SourceHandle := Registers . AX;
  1228.  
  1229.         Registers . AH := DOSFunctionCreateFile;
  1230.         Registers . CX := 0;
  1231.         Registers . DS := Seg (DestinName [1]);
  1232.         Registers . DX := Ofs (DestinName [1]);
  1233.         MsDos (Registers);
  1234.         IF ErrorReturn (Registers)                                      {004}
  1235.         THEN
  1236.             BEGIN
  1237.  
  1238.             WriteLn ('Cannot create ', DestinName);
  1239.             Halt
  1240.  
  1241.             END;
  1242.  
  1243.         DestinHandle := Registers . AX;
  1244.  
  1245.         Registers . AH := DOSFunctionReadFromFile;
  1246.         Registers . BX := SourceHandle;
  1247.         Registers . CX := BufferSize;
  1248.         Registers . DS := Seg (CopyBuffer);
  1249.         Registers . DX := Ofs (CopyBuffer);
  1250.         MsDos (Registers);
  1251.         IF ErrorReturn (Registers)                                      {004}
  1252.         THEN
  1253.             BEGIN
  1254.  
  1255.             WriteLn ('Cannot read ', SourceName);
  1256.             Halt
  1257.  
  1258.             END;
  1259.  
  1260.         TransferSize := Registers . AX;
  1261.  
  1262.         WHILE TransferSize > 0
  1263.         DO
  1264.             BEGIN
  1265.  
  1266.             Registers . AH := DOSFunctionWriteToFile;
  1267.             Registers . BX := DestinHandle;
  1268.             Registers . CX := TransferSize;
  1269.             Registers . DS := Seg (CopyBuffer);
  1270.             Registers . DX := Ofs (CopyBuffer);
  1271.             MsDos (Registers);
  1272.             IF ErrorReturn (Registers) OR                               {004}
  1273.                (TransferSize <> Registers . AX)
  1274.             THEN
  1275.                 BEGIN
  1276.  
  1277.                 WriteLn ('Cannot write ', DestinName);
  1278.                 Halt
  1279.  
  1280.                 END;
  1281.  
  1282.             Registers . AH := DOSFunctionReadFromFile;
  1283.             Registers . BX := SourceHandle;
  1284.             Registers . CX := BufferSize;
  1285.             Registers . DS := Seg (CopyBuffer);
  1286.             Registers . DX := Ofs (CopyBuffer);
  1287.             MsDos (Registers);
  1288.             IF ErrorReturn (Registers)                                  {004}
  1289.             THEN
  1290.                 BEGIN
  1291.  
  1292.                 WriteLn ('Cannot read ', SourceName);
  1293.                 Halt
  1294.  
  1295.                 END;
  1296.  
  1297.             TransferSize := Registers . AX
  1298.  
  1299.             END;
  1300.  
  1301.         (*
  1302.          *  The data have been copied.  Set the creation date  and  time
  1303.          *  to be that of the source file.
  1304.          *)
  1305.  
  1306.         Registers . AH := DOSFunctionSetFileDateTime;
  1307.         Registers . AL := 1;
  1308.         Registers . BX := DestinHandle;
  1309.         Registers . CX := FileInfo ^. Time;
  1310.         Registers . DX := FileInfo ^. Date;
  1311.         MsDos (Registers);
  1312.         IF ErrorReturn (Registers)                                      {004}
  1313.         THEN
  1314.             BEGIN
  1315.  
  1316.             WriteLn ('Cannot set date and time on ', DestinName);
  1317.             Halt
  1318.  
  1319.             END;
  1320.  
  1321.         (*
  1322.          *  Close the source and destination files
  1323.          *)
  1324.  
  1325.         Registers . AH := DOSFunctionCloseFile;
  1326.         Registers . BX := SourceHandle;
  1327.         MsDos (Registers);
  1328.         IF ErrorReturn (Registers)                                      {004}
  1329.         THEN
  1330.             BEGIN
  1331.  
  1332.             WriteLn ('Cannot close ', SourceName);
  1333.             Halt
  1334.  
  1335.             END;
  1336.  
  1337.         Registers . AH := DOSFunctionCloseFile;
  1338.         Registers . BX := DestinHandle;
  1339.         MsDos (Registers);
  1340.         IF ErrorReturn (Registers)                                      {004}
  1341.         THEN
  1342.             BEGIN
  1343.  
  1344.             WriteLn ('Cannot close ', DestinName);
  1345.             Halt
  1346.  
  1347.             END
  1348.  
  1349.         END;
  1350.  
  1351.     (*
  1352.      *  Ensure that the source and target attributes match
  1353.      *)
  1354.  
  1355.     IF (FileInfo ^. Attr AND NOT DirectoryAttrMask) <> 0
  1356.     THEN
  1357.         BEGIN
  1358.  
  1359.         Registers . AH := DOSFunctionChangeAttributes;
  1360.         Registers . AL := 1;
  1361.         Registers . DS := Seg (DestinName [1]);
  1362.         Registers . DX := Ofs (DestinName [1]);
  1363.         Registers . CX := FileInfo ^. Attr;
  1364.         MsDos (Registers);
  1365.         IF ErrorReturn (Registers)                                      {004}
  1366.         THEN
  1367.             BEGIN
  1368.  
  1369.             WriteLn ('Cannot set attributes for ', DestinName);
  1370.             Halt
  1371.  
  1372.             END
  1373.  
  1374.         END;
  1375.  
  1376.     (*
  1377.      *  If necessary, log the copying
  1378.      *)
  1379.  
  1380.     IF Logging
  1381.     THEN
  1382.         IF (FileInfo ^. Attr AND DirectoryAttrMask) <> 0
  1383.         THEN
  1384.             WriteLn ('Created directory ', DestinName)
  1385.         ELSE
  1386.             WriteLn ('Copied ', SourceName, ' to ', DestinName)
  1387.  
  1388.     END;
  1389. {.PA}
  1390. PROCEDURE ReplaceFile
  1391.    (    SourceRootDir    : PathSpec;                                    {005}
  1392.         CurrentDirectory : DirectoryEntryPtr;
  1393.         SourceFile       : FileEntryPtr;
  1394.         DestinRootDir    : PathSpec;                                    {005}
  1395.         DestinFile       : FileEntryPtr);
  1396.  
  1397. (***********************************************************************
  1398.  
  1399. FUNCTIONAL DESCRIPTION:
  1400.  
  1401.     Replaces a file on the destination drive with one of the  same  path
  1402.     specification from the source drive.
  1403.  
  1404. FORMAL PARAMETERS:
  1405.  
  1406.     SourceRootDirectory - A PathSpec expression giving the root  direct-{005}
  1407.         ory  to  which  DirectoryName  is  a  relative directory for the{005}
  1408.         source file.                                                    {005}
  1409.     CurrentDirectory - A  DirectoryEntryPtr  expression  pointing  to  a
  1410.         DirectoryEntry  object  describing  the  directory  in which the
  1411.         source and destination files are found.
  1412.     SourceFile - A  FileEntryPtr  expression  pointing  to  a  FileEntry
  1413.         object describing the source file.
  1414.     TargetRootDirectory - A PathSpec expression giving the root  direct-{005}
  1415.         ory  to which DirectoryName is a relative directory for the tar-{005}
  1416.         get file.                                                       {005}
  1417.     DestinationFile - A FileEntryPtr expression pointing to a  FileEntry
  1418.         object describing the destination file.
  1419.  
  1420. RETURN VALUE:
  1421.  
  1422.     None.
  1423.  
  1424. IMPLICIT INPUTS:
  1425.  
  1426.     None.
  1427.  
  1428. IMPLICIT OUTPUTS:
  1429.  
  1430.     None.
  1431.  
  1432. SIDE EFFECTS:
  1433.  
  1434.     None.
  1435.  
  1436. ***********************************************************************)
  1437.  
  1438.     BEGIN
  1439.  
  1440.     (*
  1441.      *  For directories, nothing need be done
  1442.      *)
  1443.  
  1444.     IF ((SourceFile ^. Attr AND DirectoryAttrMask) = 0) OR
  1445.        ((DestinFile ^. Attr AND DirectoryAttrMask) = 0)
  1446.     THEN
  1447.         BEGIN
  1448.  
  1449.         (*
  1450.          *  At least one is a file.  Delete the existing thing, and copy
  1451.          *  the new thing
  1452.          *)
  1453.  
  1454.         DeleteFile (DestinRootDir, CurrentDirectory, DestinFile);       {005}
  1455.  
  1456.         CopyFile (SourceRootDir, CurrentDirectory, SourceFile,          {005}
  1457.             DestinRootDir)                                              {005}
  1458.  
  1459.         END
  1460.  
  1461.     END;
  1462. {.PA}
  1463. PROCEDURE MatchFile
  1464.    (    SourceRootDir    : PathSpec;                                    {005}
  1465.         CurrentDirectory : DirectoryEntryPtr;
  1466.         SourceFile       : FileEntryPtr;
  1467.         DestinRootDir    : PathSpec;                                    {005}
  1468.         DestinFile       : FileEntryPtr);
  1469.  
  1470. (***********************************************************************
  1471.  
  1472. FUNCTIONAL DESCRIPTION:
  1473.  
  1474.     Modifies the non-directory attributes of a destination file to  dup-
  1475.     licate those of a source file.
  1476.  
  1477. FORMAL PARAMETERS:
  1478.  
  1479.     SourceRootDirectory - A PathSpec expression giving the root  direct-{005}
  1480.         ory  to  which  DirectoryName  is  a  relative directory for the{005}
  1481.         source file.                                                    {005}
  1482.     CurrentDirectory - A DirectoryEntryPtr expression pointing to a Dir-
  1483.         ectoryEntry object describing the directory in which the destin-
  1484.         ation file is to be found.
  1485.     SourceFile - A FileEntryPtr expression pointing to a  FileEntry  ob-
  1486.         ject describing the source file.
  1487.     TargetRootDirectory - A PathSpec expression giving the root  direct-{005}
  1488.         ory  to which DirectoryName is a relative directory for the tar-{005}
  1489.         get file.                                                       {005}
  1490.     DestinationFile - A FileEntryPtr expression pointing to a  FileEntry
  1491.         object describing the destination file.
  1492.  
  1493. RETURN VALUE:
  1494.  
  1495.     None.
  1496.  
  1497. IMPLICIT INPUTS:
  1498.  
  1499.     Logging - The BOOLEAN telling whether event logging is currently on.
  1500.  
  1501. IMPLICIT OUTPUTS:
  1502.  
  1503.     None.
  1504.  
  1505. SIDE EFFECTS:
  1506.  
  1507.     None.
  1508.  
  1509. ***********************************************************************)
  1510.  
  1511.     VAR
  1512.  
  1513.         DestinName : PathSpec;
  1514.         Registers  : RegPack;
  1515.  
  1516.     BEGIN
  1517.  
  1518.     (*
  1519.      *  Ensure the attributes match
  1520.      *)
  1521.  
  1522.     IF SourceFile ^. Attr <> DestinFile ^. Attr
  1523.     THEN
  1524.         BEGIN
  1525.  
  1526.         (*
  1527.          *  Copy attributes from the source to the destination
  1528.          *)
  1529.  
  1530.         DestinName := ConstructFileName (TargetRoot,                    {005}
  1531.             CurrentDirectory ^. Name, DestinFile ^. Name);              {005}
  1532.  
  1533.         DestinName [Length (DestinName) + 1] := #$00;
  1534.  
  1535.         Registers . AH := DOSFunctionChangeAttributes;
  1536.         Registers . AL := 1;
  1537.         Registers . DS := Seg (DestinName [1]);
  1538.         Registers . DX := Ofs (DestinName [1]);
  1539.         Registers . CX := SourceFile ^. Attr AND NOT DirectoryAttrMask; {001}
  1540.         MsDos (Registers);
  1541.         IF ErrorReturn (Registers)                                      {004}
  1542.         THEN
  1543.             BEGIN
  1544.  
  1545.             WriteLn ('Cannot change attributes on ', DestinName);
  1546.             Halt
  1547.  
  1548.             END;
  1549.  
  1550.         (*
  1551.          *  If logging, note the change
  1552.          *)
  1553.  
  1554.         IF Logging
  1555.         THEN
  1556.             WriteLn ('Modified attributes of ', DestinName)
  1557.  
  1558.         END
  1559.  
  1560.     END;
  1561. {.PA}
  1562. (***********************************************************************
  1563.  
  1564. FUNCTIONAL DESCRIPTION:
  1565.  
  1566.     Modifies a target volume to  duplicate  as  closely  as  possible  a
  1567.     source volume.
  1568.  
  1569. COMMAND LINE:
  1570.  
  1571.     <SourceRoot> <TargetRoot> [/[NO]LOG] [/[NO]ACCUMULATE]              {009}
  1572.  
  1573. RETURN VALUE:
  1574.  
  1575.     None.
  1576.  
  1577. IMPLICIT INPUTS:
  1578.  
  1579.     SourceRoot - The root directory of the source directory tree.       {009}
  1580.     TargetRoot - The root directory of the target directory tree.       {009}
  1581.     Accumulating - The BOOLEAN telling whether files on the  target  are{009}
  1582.         to be retained if they are not on the source.                   {009}
  1583.  
  1584. IMPLICIT OUTPUTS:
  1585.  
  1586.     None.
  1587.  
  1588. SIDE EFFECTS:
  1589.  
  1590.     None.
  1591.  
  1592. ***********************************************************************)
  1593.  
  1594. VAR
  1595.  
  1596.     CurrentDirectory   : DirectoryEntryPtr;
  1597.     DestinDirectory    : FileEntryQueue;
  1598.     PendingDirectories : DirectoryEntryPtr;
  1599.     SourceDirectory    : FileEntryQueue;
  1600.  
  1601. BEGIN
  1602.  
  1603. (*
  1604.  *  Print the copyright notice
  1605.  *)
  1606.  
  1607. WriteLn ('TREEDUPL version ', VersionIdentification);                   {008,002}
  1608. WriteLn;
  1609.  
  1610. (*                                                                      {009}
  1611.  *  Parse the command line                                              {009}
  1612.  *)                                                                     {009}
  1613.  
  1614. ParseCommandLine;                                                       {009}
  1615.  
  1616. (*
  1617.  *  Initialize the directory needing duplication to be the root
  1618.  *)
  1619.  
  1620. GetMem (PendingDirectories, DirectoryEntrySize);                        {005}
  1621. PendingDirectories ^. Next := NIL;
  1622. PendingDirectories ^. Name := '';                                       {005}
  1623.  
  1624. (*
  1625.  *  Copy the directories on the pending directory list
  1626.  *)
  1627.  
  1628. WHILE PendingDirectories <> NIL
  1629. DO
  1630.     BEGIN
  1631.  
  1632.     CurrentDirectory := PendingDirectories;
  1633.     PendingDirectories := PendingDirectories ^. Next;
  1634.  
  1635.     (*
  1636.      *  Expand directories on the two volumes
  1637.      *)
  1638.  
  1639.     ExpandDirectory (SourceRoot, CurrentDirectory, SourceDirectory);    {005}
  1640.     ExpandDirectory (TargetRoot, CurrentDirectory, DestinDirectory);    {005}
  1641.  
  1642.     (*
  1643.      *  Extract the directories from the source listing
  1644.      *)
  1645.  
  1646.     ExtractDirectories (CurrentDirectory, SourceDirectory,
  1647.         PendingDirectories);
  1648.  
  1649.     (*
  1650.      *  Ensure that the contents of the source and  destination  direct-
  1651.      *  ories match
  1652.      *)
  1653.  
  1654.     WHILE (SourceDirectory . Head <> NIL) OR
  1655.           (DestinDirectory . Head <> NIL)
  1656.     DO
  1657.         BEGIN
  1658.  
  1659.         IF SourceDirectory . Head = NIL
  1660.         THEN
  1661.             BEGIN
  1662.  
  1663.             (*
  1664.              *  The  source  directory  has  been  exhausted  before the
  1665.              *  destination directory.  Delete the destination directory
  1666.              *  file if not accumulating files.                         {003}
  1667.              *)
  1668.  
  1669.             IF NOT Accumulating                                         {003}
  1670.             THEN                                                        {003}
  1671.                 DeleteFile (TargetRoot, CurrentDirectory,               {005}
  1672.                     DestinDirectory . Head);
  1673.             AdvanceFile (DestinDirectory)
  1674.  
  1675.             END
  1676.         ELSE IF DestinDirectory . Head = NIL
  1677.         THEN
  1678.             BEGIN
  1679.  
  1680.             (*
  1681.              *  The destination directory has been exhausted before  the
  1682.              *  source directory.  Copy the file.
  1683.              *)
  1684.  
  1685.             CopyFile (SourceRoot, CurrentDirectory,                     {005}
  1686.                 SourceDirectory . Head, TargetRoot);                    {005}
  1687.             AdvanceFile (SourceDirectory)
  1688.  
  1689.             END
  1690.         ELSE IF SourceDirectory . Head ^. Name <
  1691.                 DestinDirectory . Head ^. Name
  1692.         THEN
  1693.             BEGIN
  1694.  
  1695.             (*
  1696.              *  The destination directory does not have a  file  of  the
  1697.              *  same name as the file in the source directory.  Copy the
  1698.              *  file.
  1699.              *)
  1700.  
  1701.             CopyFile (SourceRoot, CurrentDirectory,                     {005}
  1702.                 SourceDirectory . Head, TargetRoot);                    {005}
  1703.             AdvanceFile (SourceDirectory)
  1704.  
  1705.             END
  1706.         ELSE IF SourceDirectory . Head ^. Name >
  1707.                 DestinDirectory . Head ^. Name
  1708.         THEN
  1709.             BEGIN
  1710.  
  1711.             (*
  1712.              *  The destination directory has a file whose name  is  not
  1713.              *  in  the source directory.  Delete the destinatin file if{003}
  1714.              *  not accumulating files.                                 {003}
  1715.              *)
  1716.  
  1717.             IF NOT Accumulating
  1718.             THEN
  1719.                 DeleteFile (TargetRoot, CurrentDirectory,               {005}
  1720.                     DestinDirectory . Head);
  1721.             AdvanceFile (DestinDirectory)
  1722.  
  1723.             END
  1724.         ELSE IF NOT IDAttrMatch (SourceDirectory . Head,
  1725.                                  DestinDirectory . Head)
  1726.         THEN
  1727.             BEGIN
  1728.  
  1729.             (*
  1730.              *  The source and destination directories have files of the
  1731.              *  same  name,  but  the  identity attributes do not match.
  1732.              *  Delete the file in the destination directory,  and  copy
  1733.              *  the file from the source directory.
  1734.              *)
  1735.  
  1736.             ReplaceFile (SourceRoot, CurrentDirectory,                  {005}
  1737.                 SourceDirectory . Head, TargetRoot,                     {005}
  1738.                 DestinDirectory . Head);
  1739.             AdvanceFile (SourceDirectory);
  1740.             AdvanceFile (DestinDirectory)
  1741.             END
  1742.         ELSE
  1743.             BEGIN
  1744.  
  1745.             (*
  1746.              *  The source and destination directories have files of the
  1747.              *  same name and the identity attributes match.   Make  the
  1748.              *  MS-DOS file attributes match.
  1749.              *)
  1750.  
  1751.             MatchFile (SourceRoot, CurrentDirectory,                    {005}
  1752.                 SourceDirectory . Head, TargetRoot,                     {005}
  1753.                 DestinDirectory . Head);
  1754.             AdvanceFile (SourceDirectory);
  1755.             AdvanceFile (DestinDirectory)
  1756.  
  1757.             END
  1758.  
  1759.         END;
  1760.  
  1761.     (*
  1762.      *  The current directory has been handled.
  1763.      *)
  1764.  
  1765.     FreeMem (CurrentDirectory, DirectoryEntrySize +
  1766.         Length (CurrentDirectory ^. Name));
  1767.  
  1768.     END
  1769.  
  1770.  END.
  1771.